/******************************************************************************
* 	Copyright (c) 2007-2008 All rights reserved
*		Asemantics S.r.l
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer. 
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by 
*	 Asemantics S.r.l."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*    This product includes software developed by Asemantics S.r.l.
*    the Semantic Web company, Rome, London, Leiden and its contributors. 
*
* 5. Neither the name of the company nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Andrea Marchesini - baku@asemantics.com
*
******************************************************************************/

#include <controller.h>
#include <configure.h>
#include <process.h>
#include <istance.h>
#include <network.h>
#include <log.h>

#include <glib-object.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>

#ifdef G_OS_UNIX
#  include <sys/types.h>
#  include <unistd.h>
#  include <stdlib.h>
#  include <grp.h>
#  include <pwd.h>
#  include <signal.h>
#  include <sys/wait.h>
#endif

#ifdef G_OS_UNIX
static gboolean pid_check (TBGlobal * data, GError ** error);
static gboolean pid_check_write (TBGlobal * data, GError ** error);
static void pid_check_close (TBGlobal * data);
static gboolean permission (TBGlobal * data, GError ** error);
#endif

static gboolean controller_loop (TBGlobal * data);

/* MAIN *********************************************************************/
int
main (int argc, char **argv)
{
  GError *error = NULL;
  TBGlobal *data;

  g_thread_init (NULL);

  /* Configuration: */
  if (!(data = configure_init (argc, argv, &error)))
    {
      fprintf (stderr, "Error: %s\n", error->message);
      g_error_free (error);
      return 1;
    }

  /* Simple request: */
  if (data->operation == TB_OPERATION_LIST)
    {
      GList *list;

      for (list = data->processes; list; list = list->next)
	{
	  TBProcess *process = list->data;

	  if (process->global == TRUE && process->hidden == FALSE)
	    g_fprintf (stdout, "%s\n", process->id);
	}

      configure_free (data);
      return 0;
    }

#ifdef G_OS_UNIX
  /* Background: */
  if (data->background == TRUE && fork ())
    exit (0);
#endif

#ifdef G_OS_UNIX
  /* Pid check: */
  if (pid_check (data, &error) == FALSE)
    {
      fprintf (stderr, "Error about the Pid File: %s\n", error->message);
      configure_free (data);
      g_error_free (error);
      return 1;
    }
#endif

  /* Open the log file: */
  if (log_open (data, &error) == FALSE)
    {
      fprintf (stderr, "Error about the Log File: %s\n", error->message);
      configure_free (data);
      g_error_free (error);
      return 1;
    }

#ifdef G_OS_UNIX
  /* Permissions */
  if (permission (data, &error) == FALSE)
    {
      fprintf (stderr, "Error about the permissions: %s\n", error->message);
      configure_free (data);
      g_error_free (error);
      return 1;
    }
#endif

#ifdef G_OS_UNIX
  signal (SIGPIPE, SIG_IGN);
#endif

  if (data->net_active == TRUE && network_init (data, &error) == FALSE)
    {
      fprintf (stderr, "Error activing the network interface: %s\n",
	       error->message);
      configure_free (data);
      g_error_free (error);
      return 1;
    }

  log_write_generic (data, LOG_VERBOSE_INFO, LOG_STARTUP,
		     "pid", LOG_VERBOSE_INFO, LOG_TYPE_INTEGER, getpid (),
		     NULL);

  /* Timer function (any X milliseconds): */
  data->loop_source = g_timeout_source_new (200);
  g_source_set_callback (data->loop_source, (GSourceFunc) controller_loop,
			 data, NULL);
  g_source_attach (data->loop_source, NULL);

  /* Glib Loop: */
  data->loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data->loop);

  if (data->net_active == TRUE)
    network_close (data);

  log_write_generic (data, LOG_VERBOSE_INFO, LOG_QUIT, NULL);

  /* Close everything: */
  g_source_unref (data->loop_source);
  g_main_loop_unref (data->loop);
  log_close (data);
  pid_check_close (data);

  /* Free the memory: */
  configure_free (data);
  return 0;
}

/* CONTROLLER LOOP **********************************************************/
#ifdef G_OS_UNIX
typedef struct tb_istance_waitpid_t TBIstanceWaitpid;

struct tb_istance_waitpid_t
{
  GPid pid;
  TBIstance *istance;
  gint status;
};

static gboolean
controller_loop_waitpid (TBIstanceWaitpid * w)
{
  istance_exit (w->pid, w->status, w->istance);
  g_free (w);
  return FALSE;
}

#endif

/* This function checks the process and execs it into a new istance struct: */
static gboolean
controller_loop (TBGlobal * data)
{
  GList *list;

#ifdef TB_DEBUG
  debug (__func__, __LINE__, NULL);
#endif

#ifdef G_OS_UNIX
  for (list = data->istances; list; list = list->next)
    {
      TBIstance *istance = list->data;

      gint status;

      if (istance->pid
	  && waitpid (istance->pid, &status, WNOHANG) == istance->pid)
	{
	  TBIstanceWaitpid *w;

	  w = g_malloc0 (sizeof (TBIstanceWaitpid));
	  w->pid = istance->pid;
	  w->status = status;
	  w->istance = istance;

	  g_timeout_add (1, (GSourceFunc) controller_loop_waitpid, w);
	}
    }
#endif

  /* For any process: */
  for (list = data->processes; list; list = list->next)
    {
      TBProcess *process = list->data;

      /* Command line request or HTTP request: */
      if (process->is_to_schedule)
	{
	  process->is_to_schedule = FALSE;
	  istance_new_from_process (data, process);
	}

      /* If I have to scheduled it: */
      else if (process_is_to_schedule (data, process) == TRUE)
	istance_new_from_process (data, process);
    }

  /* For any istance waiting: */
  list = data->istances_wait;
  while (list)
    {
      TBIstanceWait *wait = list->data;
      list = list->next;

      if (istance_new_from_wait (data, wait) == FALSE)
	break;
    }

  /* Operation check: */
  if (data->operation == TB_OPERATION_PROCESS && data->istances == NULL)
    g_main_loop_quit (data->loop);

  return TRUE;
}

/* LOG SYSTEM OF GLIB *******************************************************/
GQuark
controller_error_quark (void)
{
  static GQuark q = 0;

  if (!q)
    q = g_quark_from_static_string ("controller-error-quark");

  return q;
}

/* PID CHECK ****************************************************************/
#ifdef G_OS_UNIX
static gboolean
pid_check (TBGlobal * data, GError ** error)
{
  return TRUE;

  gchar *buffer;
  gint pid;

  if (g_file_test (data->pidfile, G_FILE_TEST_EXISTS) == FALSE)
    return pid_check_write (data, error);

  if (g_file_get_contents (data->pidfile, &buffer, NULL, error) == FALSE)
    return FALSE;

  if (!(pid = atoi (buffer)))
    {
      g_set_error (error, controller_error_quark (), 0,
		   "Pid file contains strange data. Please, remove or check the file: %s",
		   data->pidfile);
      g_free (buffer);
      return FALSE;
    }

  g_free (buffer);

  /* Only for linux */
  buffer = g_strdup_printf ("/proc/%d", pid);

  if (g_file_test (buffer, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) == TRUE)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "The previous process '%d' is running!", pid);
      g_free (buffer);
      return FALSE;
    }

  g_free (buffer);
  return pid_check_write (data, error);
}

static gboolean
pid_check_write (TBGlobal * data, GError ** error)
{
  gchar *buffer;
  gboolean ret;

  buffer = g_strdup_printf ("%d", getpid ());

  ret = g_file_set_contents (data->pidfile, buffer, 0, error);
  g_free (buffer);
  return ret;
}

static void
pid_check_close (TBGlobal * data)
{
  g_remove (data->pidfile);
}

/* PERMISSIONS **************************************************************/
static gboolean
permission (TBGlobal * data, GError ** error)
{
  if (data->group)
    {
      struct group *gr;

      if (!(gr = getgrnam (data->group)) || setgid (gr->gr_gid))
	{
	  g_set_error (error, controller_error_quark (), 0,
		       "Error setting the GROUP permission of '%s'",
		       data->group);

	  return FALSE;
	}
    }

  if (data->user)
    {
      struct passwd *pw;

      if (!(pw = getpwnam (data->user)) || setuid (pw->pw_uid))
	{
	  g_set_error (error, controller_error_quark (), 0,
		       "Error setting the USER permission of '%s'",
		       data->user);

	  return FALSE;
	}
    }

  return TRUE;
}
#endif

#ifdef TB_DEBUG
/* DEBUG ********************************************************************/
GStaticMutex debug_mutex = G_STATIC_MUTEX_INIT;
GList *debug_strings = NULL;

void
debug (const gchar * func, const gint line, gchar * format, ...)
{
  GString *string;
  TBDebug *db;
  gchar *tmp;

  string = g_string_new (NULL);
  g_string_append_printf (string, "%s(%d)", func, line);

  if (format)
    {
      va_list va;

      string = g_string_append (string, " - ");

      va_start (va, format);
      g_string_append_vprintf (string, format, va);
      va_end (va);
    }

  tmp = g_string_free (string, FALSE);

  g_static_mutex_lock (&debug_mutex);

  if (debug_strings)
    {
      db = g_list_last (debug_strings)->data;

      if (!strcmp (db->message, tmp))
	{
	  db->repeat++;

	  g_free (tmp);
	  g_static_mutex_unlock (&debug_mutex);
	  return;
	}
    }

  while (g_list_length (debug_strings) > TB_DEBUG)
    {
      db = debug_strings->data;

      g_free (db->message);
      g_free (db);

      debug_strings = g_list_remove (debug_strings, db);
    }

  db = g_malloc0 (sizeof (TBDebug));
  db->message = tmp;
  db->time = time (NULL);
  db->repeat++;

  debug_strings = g_list_append (debug_strings, db);

  g_static_mutex_unlock (&debug_mutex);
}

#endif

/* EOF */
